/*
@title: untitled taiko game
@description: "Untitled Taiko Game" is a rhythm music game inspired by titles like "Taiko no Tatsujin" and "osu!taiko." Players must use designated keys to hit notes of corresponding colors as they move across the screen, following the rhythm of the music.
@author: Joey Lent
@tags: ['music']
@addedOn: 2023-01-10
@img: ""
*/

/**************************
 *         About          *
 **************************/

// A Taiko no Tatsujin recreation :D
// (also inspired by osu!taiko)
// ((mostly osu!taiko))

// Warning: This game lags when played on Firefox.

// How to play:
//   You have 4 keys, 2 for red and 2 for blue, as shown on the intro screen.
//
//   As the notes travel across the screen, you need to tap the key that is
//   the same color as the note to the rhythm of the music. Use the indicator
//   on the left for reference of when to tap.

// Keybinds:
//   A L - Blue drum
//   D J - Red drum
//   W I - Tap either key 3 times when in-game to quit to menu
// Otherwise, keybinds are indicated on-screen.

/**************************
 *     Game Constants     *
 **************************/

                          // Timing for KEYPRESSES
const MILLIS_GREAT =  50; //   0 >= x > 50   -> GREAT
const MILLIS_OK    = 120; //  50 >= x > 120  -> OK
const MILLIS_MISS  = 150; // 120 >= x > 135  -> MISS
                          // 135 >= x        -> ignored

/**************************
 *        Beatmaps        *
 **************************/

// Each note in [beatmap].difficulties.notes is defined as one of the following:
//   [offset, note, (scroll speed)]
//
// Offset - Time in milliseconds since start of song
// Note   - 1 (blue) or 0 (red)
// 
// There is an optional scroll speed which defines how long a note takes to travel
// across the screen. If excluded, it will assume the latest previously defined
// scroll speed or 1.
//
// A scroll speed of 1 means that the note takes 1 second to travel across the screen.
// 0.5 means that it takes 2 seconds. 2 means that the note takes half a second.

const BEATMAPS = [
  {
    title: "Korobeiniki",
    artist: "Folk song",
    music: [
tune`
0,
352.94199999999995: E5^334.55960416666665,
176.47099999999995: B4^166.9121541666666,
176.47100000000003: C5^166.9121541666667,
176.47100000000003: D5^166.9121541666667,
176.47099999999983: E5^166.9121541666667,
176.47100000000003: C5^166.91215416666694,
176.47100000000003: B4^166.9121541666667,
352.94200000000006: A4^334.55960416666676,
176.47099999999983: A4^166.91215416666648,
176.47099999999983: C5^166.91215416666648,
352.94200000000006: E5^334.559604166667,
176.47100000000026: D5^166.91215416666648,
176.47099999999983: C5^166.91215416666648,
352.94200000000006: B4^334.55960416666653,
176.47099999999983: B4^166.91215416666648,
176.47100000000026: C5^166.91215416666648,
352.94199999999967: D5^334.5596041666661,
352.94199999999967: E5^334.55960416666653,
352.9420000000005: C5^334.55960416666653,
352.94199999999967: A4^334.55960416666653,
882.3550000000005: A4^669.8545041666666,
352.94199999999967: D5^334.55960416666653,
176.47100000000026: F5^166.91215416666694,
352.94199999999967: A5^334.55960416666653,
176.47100000000026: G5^166.91215416666694,
352.94199999999967: F5^166.91215416666606,
352.9420000000005: E5^334.55960416666744,
176.47099999999938: C5^166.91215416666606,
352.9420000000005: E5^334.55960416666744,
176.47099999999938: D5^166.91215416666694,
176.47099999999938: C5^166.91215416666694,
352.9420000000005: B4^334.55960416666744,
176.47100000000114: B4^166.91215416666694,
176.47099999999938: C5^166.91215416666518,
352.9420000000005: D5^334.55960416666744,
352.94199999999876: E5^334.5596041666656,
352.9420000000005: C5^334.55960416666744,
352.9420000000005: A4^334.55960416666744,
705.8839999999993: A4^669.8545041666666,
352.9420000000005: E5^334.55960416666744,
176.47099999999938: B4^166.91215416666518,
176.47099999999938: C5^166.91215416666694,
176.47100000000114: D5^166.91215416666694,
176.47099999999938: E5^166.91215416666694,
176.47099999999938: C5^166.91215416666694,
176.47100000000114: B4^166.91215416666694,
352.9420000000005: A4^334.5596041666656,
176.47099999999938: A4^166.91215416666518,
176.47099999999938: C5^166.91215416666694,
352.9420000000005: E5^334.55960416666744,
176.47099999999938: D5^166.91215416666518,
176.47100000000114: C5^166.91215416666694,
352.94199999999876: B4^334.5596041666656,
176.47099999999938: B4^166.91215416666694,
176.47100000000114: C5^166.91215416666694,
352.9420000000005: D5^334.5596041666656,
352.94199999999876: E5^334.5596041666656,
352.9420000000005: C5^334.55960416666744,
352.9420000000005: A4^334.5596041666656,
882.3550000000005: A4^669.8545041666648,
352.94199999999876: D5^334.55960416666744,
176.47099999999938: F5^166.91215416666694,
352.9420000000023: A5^334.55960416666744,
176.47099999999938: G5^166.91215416666694,
352.94199999999876: F5^166.91215416666694,
352.94199999999876: E5^334.55960416666744,
176.47099999999938: C5^166.91215416666694,
352.9420000000023: E5^334.55960416667097,
176.47099999999938: D5^166.91215416666694,
176.47099999999938: C5^166.91215416666694,
352.9420000000023: B4^334.55960416666744,
176.47099999999938: B4^166.91215416666694,
176.47099999999938: C5^166.91215416666694,
352.94199999999876: D5^334.55960416666744,
352.94199999999876: E5^334.55960416666744,
352.9420000000023: C5^334.55960416667097,
352.9420000000023: A4^334.55960416666744,
705.8839999999975: A4^669.8545041666648,
352.94199999999876: E5^334.55960416666386,
176.47099999999938: B4^166.91215416666694,
176.47099999999938: C5^166.9121541666705,
352.9420000000023: D5^334.55960416666744,
352.9420000000023: E5^334.55960416666744,
352.94199999999876: C5^334.55960416666386,
176.47099999999938: A4^166.91215416666694,
176.47099999999938: C5^166.91215416666694,
352.94199999999876: E5^334.55960416666386,
352.9420000000023: A5^334.55960416666744,
352.94199999999876: G#5^334.55960416666744,
176.47100000000293: F5^166.9121541666705,
176.47099999999938: E5^166.91215416666694,
352.94199999999876: D5^334.55960416666386,
352.94199999999876: C5^334.55960416666386,
352.94199999999876: E5^334.55960416666386,
352.9420000000023: D#5^334.55960416666744,
352.94199999999876: D5^334.55960416666386,
352.94199999999876: C#5^334.55960416666744,
352.9420000000023: C5^334.55960416666744,
176.47099999999938: A4^166.91215416666694,
176.47099999999938: B4^166.91215416666694,
352.9420000000023: C5^334.55960416666744,
352.94199999999876: A4^334.55960416666386,
352.94199999999876: B4^334.55960416666386,
176.47099999999938: E4^166.91215416666694,
176.47100000000293: G#4^166.9121541666705,
352.94199999999876: B4^334.55960416666386,
352.94199999999876: G#4^334.55960416666386,
352.9420000000023: A4^334.55960416666744,
352.94199999999876: G4^334.55960416666386,
352.9420000000023: F4^334.5596041666603,
352.94199999999876: D4^334.55960416666386,
352.94199999999876: A4^334.55960416666386,
352.94199999999876: G4^334.55960416667097,
352.94199999999876: F4^334.55960416667097,
352.94199999999876: D4^334.55960416667097,
176.47100000000648: E4^166.91215416666694,
176.47099999999227: D#4^166.91215416666694,
176.47100000000648: E4^166.91215416666694,
176.47099999999938: F4^166.91215416666694,
176.47099999999938: G#4^166.91215416665983,
176.47099999999938: A4^166.91215416666694,
176.47099999999938: B4^166.91215416666694,
176.47099999999938: C5^166.91215416666694,
176.47099999999938: D5^166.91215416666694,
176.47100000000648: C5^166.91215416666694,
176.47099999999227: D5^166.91215416665983,
176.47100000000648: C5^166.91215416666694,
176.47099999999227: B4^166.91215416666694,
176.47100000000648: A4^166.91215416666694,
176.47099999999938: G#4^166.91215416666694,
176.47099999999938: E4^166.91215416665983,
176.47099999999938: G#4^166.91215416666694,
176.47099999999938: A4^166.91215416665983,
176.47099999999938: B4^166.91215416666694,
176.47099999999938: C5^166.91215416666694,
176.47100000000648: D5^166.91215416666694,
176.47099999999227: D#5^166.91215416665983,
176.47100000000648: E5^166.91215416666694,
176.47099999999227: G#5^166.91215416665983,
705.8840000000046: E6^669.8545041666648,
352.94199999999876: E5^334.55960416666386,
176.47099999999938: B4^166.91215416666694,
176.47100000000648: C5^166.91215416666694,
176.47099999999227: D5^166.91215416665983,
176.47100000000648: E5^166.91215416666694,
176.47099999999227: C5^166.91215416665983,
176.47100000000648: B4^166.91215416666694,
352.94199999999876: A4^334.55960416666386,
176.47099999999938: A4^166.91215416666694,
176.47099999999938: C5^166.91215416665983,
352.94199999999876: E5^334.55960416667097,
176.47099999999938: D5^166.91215416666694,
176.47099999999938: C5^166.91215416666694,
352.94199999999876: B4^334.55960416667097,
176.47100000000648: B4^166.91215416666694,
176.47099999999227: C5^166.91215416666694,
352.94200000000586: D5^334.55960416667097,
352.94199999999876: E5^334.55960416666386,
352.94200000000586: C5^334.55960416666386,
352.94199999999876: A4^334.55960416665675,
882.3549999999898: A4^669.8545041666648,
352.94199999999876: D5^334.55960416667097,
176.47100000000648: F5^166.91215416666694,
352.94199999999876: A5^334.55960416667097,
176.47100000000648: G5^166.91215416666694,
352.94199999999876: F5^166.91215416666694,
352.94199999999876: E5^334.55960416667097,
176.47099999999227: C5^166.91215416666694,
352.94199999999876: E5^334.55960416667097,
176.47100000000648: D5^166.91215416666694,
176.47100000000648: C5^166.91215416666694,
352.94199999999876: B4^334.55960416665675,
176.47099999999227: B4^166.91215416665273,
176.47100000000648: C5^166.91215416666694,
352.94199999999876: D5^334.55960416667097,
352.94199999999876: E5^334.55960416667097,
352.94199999999876: C5^334.55960416667097,
352.94199999999876: A4^334.55960416667097,
882.355000000004: A4^669.8545041666648,
352.94199999999876: B4^334.55960416667097,
176.47099999999227: C5^166.91215416666694,
352.94199999999876: D5^334.55960416667097,
529.4130000000052: E5^334.55960416667097,
352.94199999999876: B4^334.55960416667097,
176.47100000000648: C5^166.91215416666694,
352.94199999999876: D5^334.55960416667097,
529.412999999991: E5^334.55960416667097,
352.94200000001297: B4^334.55960416667097,
176.47099999999227: C5^166.91215416665273,
352.94199999999876: D5^334.55960416667097,
352.94199999999876: D#5^334.55960416667097,
0: E5^166.91215416666694,
176.47100000000648: E6^166.91215416666694,
0: D#5^166.91215416666694,
176.47099999999227: D#6^166.91215416666694,
0: D5^166.91215416666694,
176.47100000000648: D6^166.91215416666694,
0: C#5^166.91215416666694,
176.47099999999227: C#6^166.91215416666694,
0: C5^166.91215416666694,
176.47100000000648: C6^166.91215416666694,
0: B4^166.91215416666694,
176.47099999999227: B5^166.91215416666694,
0: A#4^166.91215416666694,
176.47100000000648: A#5^166.91215416666694,
0: A4^166.91215416666694,
176.47100000000648: A5^166.91215416666694,
0: G#4^166.91215416665273,
176.47099999999227: G#5^166.91215416665273,
0: G4^166.91215416666694,
176.47100000000648: G5^166.91215416666694,
0: F#4^166.91215416666694,
176.47099999999227: F#5^166.91215416666694,
0: F4^166.91215416666694,
176.47100000000648: F5^166.91215416666694,
0: E4^334.55960416665675,
705.8839999999975: E5^334.55960416665675,
176.47099999999227: E6^166.91215416666694,
176.47100000000648: F6^166.91215416666694,
176.47100000000648: E6^166.91215416666694,
176.47099999999227: C6^166.91215416665273,
176.47100000000648: D6^166.91215416666694,
176.47099999999227: E6^166.91215416666694,
176.47100000000648: D6^166.91215416666694,
176.47099999999227: B5^166.91215416666694,
176.47100000000648: C6^166.91215416666694,
176.47099999999227: D6^166.91215416666694,
176.47100000000648: C6^166.91215416666694,
176.47099999999227: A5^166.91215416666694,
176.47100000000648: B5^166.91215416666694,
176.47100000000648: C6^166.91215416666694,
176.47099999999227: B5^166.91215416665273,
176.47100000000648: G#5^166.91215416666694,
176.47099999999227: A5^166.91215416665273,
176.47100000000648: G#5^166.91215416666694,
176.47099999999227: A5^166.91215416666694,
176.47100000000648: B5^166.91215416666694,
58.08837083333174: C6^57.35307499999465,
58.82366666666883: D6^57.35307499999465,
59.55896249999171: C6^57.35307499999465,
176.47100000000648: B5^166.91215416666694,
176.47099999999227: C6^166.91215416666694,
176.47100000000648: D6^166.91215416666694,
176.47099999999227: E6^166.91215416666694,
176.47100000000648: F6^166.91215416666694,
176.47100000000648: E6^166.91215416666694,
176.47099999999227: C6^166.91215416665273,
176.47100000000648: D6^166.91215416666694,
176.47099999999227: E6^166.91215416666694,
176.47100000000648: D6^166.91215416666694,
176.47099999999227: B5^166.91215416666694,
176.47100000000648: C6^166.91215416666694,
176.47099999999227: D6^166.91215416666694,
176.47100000000648: C6^166.91215416666694,
176.47099999999227: A5^166.91215416666694,
176.47100000000648: B5^166.91215416666694,
176.47100000000648: C6^166.91215416666694,
176.47099999999227: B5^166.91215416665273,
176.47100000000648: G#5^166.91215416666694,
176.47099999999227: A5^166.91215416666694,
176.47100000000648: B5^166.91215416666694,
176.47099999999227: C6^166.91215416666694,
176.47100000000648: B5^166.91215416666694,
352.94199999999876: A5^334.55960416665675,
352.94199999999876: E4^334.55960416665675,
176.47100000000648: E5^166.91215416666694,
176.47099999999227: F5^166.91215416665273,
176.47100000000648: E5^166.91215416666694,
176.47099999999227: C5^166.91215416665273,
176.47100000000648: D5^166.91215416666694,
176.47099999999227: E5^166.91215416666694,
176.47100000000648: D5^166.91215416666694,
176.47099999999227: B4^166.91215416666694,
176.47100000000648: C5^166.91215416666694,
176.47099999999227: D5^166.91215416666694,
176.47100000000648: C5^166.91215416666694,
176.47100000000648: A4^166.91215416666694,
176.47099999999227: B4^166.91215416665273,
176.47100000000648: C5^166.91215416666694,
176.47099999999227: B4^166.91215416665273,
176.47100000000648: G#4^166.91215416666694,
176.47099999999227: A4^166.91215416666694,
176.47100000000648: G#4^166.91215416666694,
176.47099999999227: A4^166.91215416666694,
176.47100000000648: B4^166.91215416666694,
58.08837083333174: C5^57.35307499999465,
58.82366666666883: D5^57.35307499999465,
59.55896249999171: C5^57.35307499999465,
176.47100000000648: B4^166.91215416666694,
176.47099999999227: C5^166.91215416666694,
176.47100000000648: D5^166.91215416666694,
176.47100000000648: E5^166.91215416666694,
176.47099999999227: F5^166.91215416665273,
176.47100000000648: E5^166.91215416666694,
176.47099999999227: C5^166.91215416666694,
176.47100000000648: D5^166.91215416666694,
176.47099999999227: E5^166.91215416666694,
176.47100000000648: D5^166.91215416666694,
176.47099999999227: B4^166.91215416666694,
176.47100000000648: C5^166.91215416666694,
176.47099999999227: D5^166.91215416666694,
176.47100000000648: C5^166.91215416666694,
176.47100000000648: A4^166.91215416666694,
176.47099999999227: B4^166.91215416665273,
176.47100000000648: C5^166.91215416666694,
176.47099999999227: B4^166.91215416666694,
176.47100000000648: G#4^166.91215416666694,
176.47099999999227: A4^166.91215416666694,
176.47100000000648: B4^166.91215416666694,
176.47099999999227: C5^166.91215416666694,
176.47100000000648: E5^166.91215416666694,
3520.9420000000023: A5^669.8545041666648,
`,
tune`
0,
176.47099999999998: B3^166.91215416666665,
176.47099999999998: E4^166.91215416666665,
176.47099999999995: D4^166.9121541666666,
176.47100000000003: E4^166.9121541666667,
176.47100000000003: E3^166.9121541666667,
176.47099999999983: E4^166.9121541666667,
176.47100000000003: B3^166.91215416666694,
176.47100000000003: E4^166.9121541666667,
176.47100000000003: A3^166.91215416666648,
176.47100000000003: E4^166.91215416666648,
176.47099999999983: C4^166.91215416666648,
176.47099999999983: E4^166.91215416666648,
176.47100000000026: E3^166.91215416666694,
176.47099999999983: E4^166.91215416666648,
176.47100000000026: C4^166.91215416666648,
176.47099999999983: E4^166.91215416666648,
176.47099999999983: B3^166.91215416666648,
176.47100000000026: E4^166.91215416666694,
176.47099999999983: D4^166.91215416666648,
176.47100000000026: E4^166.91215416666648,
176.47099999999983: E3^166.91215416666648,
176.47099999999983: E4^166.91215416666648,
176.47100000000026: B3^166.91215416666694,
176.47099999999938: E4^166.91215416666606,
176.47100000000026: A3^166.91215416666694,
176.47100000000026: E4^166.91215416666694,
176.47100000000026: C4^166.91215416666606,
176.47099999999938: E4^166.91215416666606,
176.47100000000026: A3^166.91215416666694,
176.47100000000026: E4^166.91215416666694,
176.47099999999938: C4^166.91215416666606,
176.47100000000026: E4^166.91215416666694,
176.47100000000026: D3^166.91215416666694,
176.47099999999938: F4^166.91215416666606,
176.47100000000026: D4^166.91215416666694,
176.47100000000026: F4^166.91215416666694,
176.47099999999938: A3^166.91215416666606,
176.47100000000026: F4^166.91215416666694,
176.47100000000026: D4^166.91215416666694,
176.47100000000026: F4^166.91215416666606,
176.47099999999938: A3^166.91215416666606,
176.47100000000026: E4^166.91215416666694,
176.47100000000026: C4^166.91215416666606,
176.47099999999938: E4^166.91215416666606,
176.47100000000026: E3^166.91215416666694,
176.47100000000026: E4^166.91215416666606,
176.47099999999938: C4^166.91215416666694,
176.47099999999938: E4^166.91215416666694,
176.47100000000114: B3^166.91215416666694,
176.47099999999938: E4^166.91215416666694,
176.47100000000114: D4^166.91215416666694,
176.47099999999938: E4^166.91215416666518,
176.47099999999938: E3^166.91215416666694,
176.47100000000114: E4^166.91215416666694,
176.47099999999938: B3^166.91215416666518,
176.47099999999938: E4^166.91215416666694,
176.47100000000114: A3^166.91215416666694,
176.47099999999938: E4^166.91215416666518,
176.47099999999938: C4^166.91215416666694,
176.47100000000114: E4^166.91215416666694,
176.47099999999938: A3^166.91215416666518,
176.47099999999938: E4^166.91215416666694,
176.47100000000114: C4^166.91215416666694,
176.47099999999938: E4^166.91215416666518,
176.47099999999938: B3^166.91215416666694,
176.47100000000114: E4^166.91215416666694,
176.47099999999938: D4^166.91215416666518,
176.47099999999938: E4^166.91215416666694,
176.47100000000114: E3^166.91215416666694,
176.47099999999938: E4^166.91215416666694,
176.47099999999938: B3^166.91215416666694,
176.47100000000114: E4^166.91215416666694,
176.47099999999938: A3^166.91215416666518,
176.47100000000114: E4^166.91215416666694,
176.47099999999938: C4^166.91215416666518,
176.47099999999938: E4^166.91215416666694,
176.47099999999938: E3^166.91215416666694,
176.47100000000114: E4^166.91215416666694,
176.47099999999938: C4^166.91215416666518,
176.47100000000114: E4^166.91215416666694,
176.47099999999938: B3^166.91215416666518,
176.47099999999938: E4^166.91215416666694,
176.47099999999938: D4^166.91215416666694,
176.47100000000114: E4^166.91215416666694,
176.47099999999938: E3^166.91215416666694,
176.47100000000114: E4^166.91215416666694,
176.47099999999938: B3^166.91215416666694,
176.47099999999938: E4^166.91215416666694,
176.47099999999938: A3^166.91215416666694,
176.47100000000114: E4^166.91215416666694,
176.47100000000114: C4^166.91215416666518,
176.47099999999938: E4^166.91215416666694,
176.47099999999938: A3^166.91215416666694,
176.47099999999938: E4^166.91215416666694,
176.47099999999938: C4^166.91215416666694,
176.47099999999938: E4^166.91215416666694,
176.47100000000293: D3^166.91215416666694,
176.47099999999938: F4^166.91215416666338,
176.47099999999938: D4^166.91215416666694,
176.47099999999938: F4^166.91215416666694,
176.47099999999938: A3^166.91215416666694,
176.47100000000293: F4^166.91215416666694,
176.47099999999938: D4^166.91215416666694,
176.47099999999938: F4^166.91215416666694,
176.47099999999938: A3^166.91215416666694,
176.47099999999938: E4^166.91215416666694,
176.47099999999938: C4^166.91215416666694,
176.47099999999938: E4^166.91215416666694,
176.47100000000293: E3^166.91215416666694,
176.47099999999938: E4^166.91215416666338,
176.47099999999938: C4^166.91215416666694,
176.47099999999938: E4^166.91215416666694,
176.47100000000293: B3^166.91215416666694,
176.47099999999938: E4^166.91215416666694,
176.47099999999938: D4^166.91215416666694,
176.47099999999938: E4^166.91215416666694,
176.47099999999938: E3^166.91215416666694,
176.47099999999938: E4^166.91215416666694,
176.47099999999938: B3^166.91215416666694,
176.47099999999938: E4^166.91215416666694,
176.47100000000293: A3^166.91215416666694,
176.47099999999938: E4^166.91215416666338,
176.47099999999938: C4^166.91215416666694,
176.47100000000293: E4^166.91215416666694,
176.47099999999938: A3^166.91215416666338,
176.47099999999938: E4^166.91215416666694,
176.47099999999938: C4^166.91215416666694,
176.47099999999938: E4^166.91215416666694,
176.47099999999938: E3^166.91215416666694,
352.94199999999876: B3^334.55960416666386,
176.47099999999938: B3^166.9121541666705,
176.47100000000293: E3^166.9121541666705,
352.94199999999876: B3^334.55960416666744,
176.47100000000293: B3^166.9121541666705,
176.47099999999938: A3^166.91215416666694,
352.94199999999876: C4^334.55960416666386,
176.47099999999938: C4^166.91215416666694,
176.47099999999938: A3^166.91215416666694,
352.94199999999876: C4^334.55960416666744,
176.47100000000293: C4^166.9121541666705,
176.47099999999938: E3^166.91215416666694,
352.9420000000023: G#3^334.55960416666744,
176.47099999999938: G#3^166.91215416666694,
176.47099999999938: E3^166.91215416666694,
352.94199999999876: G#3^334.55960416666386,
176.47099999999938: G#3^166.91215416666694,
176.47099999999938: C4^166.91215416666694,
176.47099999999938: E4^166.91215416666694,
176.47100000000293: B3^166.9121541666705,
176.47099999999938: E4^166.91215416666694,
176.47099999999938: A#3^166.91215416666694,
176.47099999999938: E4^166.91215416666694,
176.47099999999938: A3^166.91215416666694,
176.47099999999938: E4^166.9121541666705,
176.47100000000293: F3^166.9121541666705,
352.94199999999876: A3^334.55960416666386,
176.47099999999938: A3^166.91215416666694,
176.47099999999938: F3^166.91215416666694,
352.9420000000023: A3^334.55960416666744,
176.47099999999938: A3^166.91215416666694,
176.47099999999938: E3^166.91215416666694,
352.94199999999876: G#3^334.55960416666744,
176.47100000000293: G#3^166.9121541666705,
176.47099999999938: E3^166.91215416666694,
352.94199999999876: G#3^334.55960416666386,
176.47099999999938: G#3^166.91215416666694,
176.47099999999938: D3^166.9121541666705,
352.9420000000023: F3^334.55960416666744,
176.47099999999938: F3^166.91215416666694,
176.47099999999938: D3^166.91215416666694,
352.94199999999523: F3^334.55960416666744,
176.47100000000648: F3^166.91215416667404,
176.47099999999938: B2^166.91215416666694,
352.94199999999876: D3^334.55960416666386,
176.47099999999938: D3^166.91215416666694,
176.47099999999938: B2^166.91215416666694,
352.94200000000586: D3^334.55960416666386,
176.47099999999227: D3^166.91215416666694,
1411.768000000002: E2^1340.4443041666668,
1411.768000000002: E2^1340.4443041666668,
176.47099999999938: E3^166.91215416666694,
176.47099999999938: F3^166.91215416665983,
176.47099999999938: G3^166.91215416666694,
176.47099999999938: G#3^166.91215416666694,
176.47100000000648: B3^166.91215416666694,
176.47099999999227: C4^166.91215416665983,
176.47100000000648: E4^166.91215416666694,
176.47099999999227: G#4^166.91215416665983,
705.8840000000046: E5^669.8545041666648,
176.47099999999938: E2^166.91215416665983,
176.47099999999938: E3^166.91215416666694,
176.47099999999938: G#2^166.91215416666694,
176.47100000000648: E3^166.91215416666694,
176.47099999999227: B2^166.91215416665983,
176.47100000000648: E3^166.91215416666694,
176.47099999999227: G#2^166.91215416665983,
176.47100000000648: E3^166.91215416666694,
176.47099999999227: A2^166.91215416666694,
176.47100000000648: A3^166.91215416666694,
176.47099999999938: C3^166.91215416666694,
176.47099999999938: A3^166.91215416665983,
176.47099999999938: E3^166.91215416666694,
176.47099999999938: A3^166.91215416666694,
176.47099999999938: C3^166.91215416666694,
176.47099999999938: A3^166.91215416666694,
176.47100000000648: G#3^166.91215416666694,
176.47099999999227: E4^166.91215416665983,
176.47100000000648: G#3^166.91215416666694,
176.47099999999227: E4^166.91215416666694,
176.47100000000648: B3^166.91215416666694,
176.47099999999938: E4^166.91215416666694,
176.47099999999938: G#3^166.91215416665983,
176.47099999999938: E4^166.91215416666694,
176.47099999999938: A3^166.91215416665983,
176.47100000000648: E4^166.91215416666694,
176.47099999999227: E3^166.91215416665273,
176.47100000000648: C4^166.91215416666694,
176.47099999999227: C3^166.91215416666694,
176.47100000000648: A3^166.91215416666694,
176.47099999999227: A2^166.91215416666694,
176.47100000000648: A3^166.91215416666694,
176.47099999999227: D2^166.91215416666694,
176.47100000000648: F3^166.91215416666694,
176.47099999999227: D2^166.91215416666694,
176.47100000000648: F3^166.91215416666694,
176.47100000000648: D3^166.91215416666694,
176.47099999999227: F3^166.91215416665273,
176.47100000000648: A2^166.91215416666694,
176.47099999999227: F3^166.91215416666694,
176.47100000000648: A2^166.91215416666694,
176.47099999999227: A3^166.91215416666694,
176.47100000000648: C3^166.91215416666694,
176.47099999999227: A3^166.91215416666694,
176.47100000000648: E3^166.91215416666694,
176.47099999999227: A3^166.91215416666694,
176.47100000000648: C3^166.91215416666694,
176.47100000000648: A3^166.91215416666694,
176.47099999999227: E2^166.91215416665273,
176.47100000000648: E3^166.91215416666694,
176.47099999999227: G#2^166.91215416665273,
176.47100000000648: E3^166.91215416666694,
176.47099999999227: B2^166.91215416666694,
176.47100000000648: E3^166.91215416666694,
176.47099999999227: G#2^166.91215416666694,
176.47100000000648: E3^166.91215416666694,
176.47099999999227: A2^166.91215416666694,
176.47100000000648: A3^166.91215416666694,
176.47099999999227: C3^166.91215416666694,
176.47100000000648: A3^166.91215416666694,
176.47100000000648: E3^166.91215416666694,
176.47099999999227: A3^166.91215416665273,
176.47100000000648: C3^166.91215416666694,
176.47099999999227: A3^166.91215416666694,
176.47100000000648: B3^166.91215416666694,
176.47099999999227: F4^166.91215416666694,
176.47100000000648: D4^166.91215416666694,
176.47099999999227: F4^166.91215416666694,
176.47100000000648: B3^166.91215416666694,
176.47099999999227: F4^166.91215416666694,
176.47100000000648: D4^166.91215416666694,
176.47100000000648: F4^166.91215416666694,
176.47099999999227: G#3^166.91215416665273,
176.47100000000648: F4^166.91215416666694,
176.47099999999227: B3^166.91215416666694,
176.47100000000648: D4^166.91215416666694,
176.47099999999227: G#3^166.91215416666694,
176.47100000000648: F4^166.91215416666694,
176.47099999999227: B3^166.91215416666694,
176.47100000000648: D4^166.91215416666694,
176.47099999999227: F3^166.91215416666694,
176.47100000000648: D4^166.91215416666694,
176.47100000000648: B3^166.91215416666694,
176.47099999999227: D4^166.91215416665273,
176.47100000000648: F3^166.91215416666694,
176.47099999999227: D4^166.91215416665273,
176.47100000000648: D#3^166.91215416666694,
176.47099999999227: D#4^166.91215416666694,
176.47100000000648: E3^166.91215416666694,
176.47099999999227: D#3^166.91215416666694,
176.47100000000648: D3^166.91215416666694,
176.47099999999227: C#3^166.91215416666694,
176.47100000000648: C3^166.91215416666694,
176.47099999999227: B2^166.91215416666694,
176.47100000000648: A#2^166.91215416666694,
176.47100000000648: A2^166.91215416666694,
176.47099999999227: G#2^166.91215416665273,
176.47100000000648: G2^166.91215416666694,
176.47099999999227: F#2^166.91215416666694,
176.47100000000648: F2^166.91215416666694,
352.94199999999876: E2^334.55960416665675,
352.94199999999876: E1^334.55960416665675,
352.94199999999876: A2^334.55960416665675,
0: C3^334.55960416665675,
0: E3^334.55960416665675,
352.94199999999876: A3^334.55960416665675,
352.94199999999876: E2^334.55960416667097,
0: B2^334.55960416667097,
0: E3^334.55960416667097,
352.94199999999876: G#3^334.55960416667097,
352.94199999999876: A2^334.55960416667097,
0: C3^334.55960416667097,
0: E3^334.55960416667097,
352.94199999999876: A3^334.55960416667097,
352.94200000001297: E2^334.55960416667097,
0: B2^334.55960416665675,
0: E3^334.55960416665675,
352.94199999999876: G#3^334.55960416665675,
176.47099999999227: A2^166.91215416665273,
0: C3^166.91215416666694,
0: E3^166.91215416666694,
176.47100000000648: A3^166.91215416666694,
176.47099999999227: E2^166.91215416666694,
0: G#2^166.91215416666694,
0: B2^166.91215416666694,
176.47100000000648: E3^166.91215416666694,
176.47099999999227: A2^166.91215416666694,
0: C3^166.91215416666694,
0: E3^166.91215416666694,
176.47100000000648: A3^166.91215416666694,
176.47099999999227: E2^166.91215416666694,
0: G#2^166.91215416666694,
0: B2^166.91215416666694,
176.47100000000648: E3^166.91215416666694,
352.94199999999876: A2^334.55960416665675,
0: C3^334.55960416665675,
0: E3^334.55960416665675,
352.94199999999876: A3^334.55960416665675,
352.94199999999876: E2^334.55960416667097,
0: B2^334.55960416667097,
0: E3^334.55960416667097,
352.94199999999876: G#3^334.55960416667097,
352.94199999999876: A2^334.55960416667097,
0: C3^334.55960416667097,
0: E3^334.55960416667097,
352.94199999999876: A3^334.55960416667097,
352.94200000001297: F2^334.55960416667097,
0: B2^334.55960416665675,
352.94199999999876: F3^334.55960416665675,
176.47099999999227: E1^166.91215416666694,
176.47100000000648: F1^166.91215416666694,
176.47099999999227: G1^166.91215416666694,
176.47100000000648: G#1^166.91215416666694,
352.94199999999876: A1^334.55960416665675,
352.94199999999876: E1^334.55960416665675,
352.94199999999876: A2^334.55960416665675,
0: C3^334.55960416667097,
0: E3^334.55960416667097,
352.94199999999876: A3^334.55960416667097,
352.94199999999876: E2^334.55960416667097,
0: B2^334.55960416667097,
0: E3^334.55960416667097,
352.94199999999876: G#3^334.55960416667097,
352.94199999999876: A2^334.55960416667097,
0: C3^334.55960416667097,
0: E3^334.55960416667097,
352.94200000001297: A3^334.55960416667097,
352.94199999999876: E2^334.55960416665675,
0: B2^334.55960416665675,
0: E3^334.55960416665675,
352.94199999999876: G#3^334.55960416665675,
176.47099999999227: A2^166.91215416666694,
0: C3^166.91215416666694,
0: E3^166.91215416666694,
176.47100000000648: A3^166.91215416666694,
176.47099999999227: E2^166.91215416666694,
0: G#2^166.91215416666694,
0: B2^166.91215416666694,
176.47100000000648: E3^166.91215416666694,
176.47099999999227: A2^166.91215416666694,
0: C3^166.91215416666694,
0: E3^166.91215416666694,
176.47100000000648: A3^166.91215416666694,
176.47099999999227: E2^166.91215416666694,
0: G#2^166.91215416666694,
0: B2^166.91215416666694,
176.47100000000648: E3^166.91215416666694,
352.94199999999876: A2^334.55960416665675,
0: C3^334.55960416667097,
0: E3^334.55960416667097,
352.94199999999876: A3^334.55960416667097,
352.94199999999876: E2^334.55960416667097,
0: B2^334.55960416667097,
0: E3^334.55960416667097,
352.94199999999876: G#3^334.55960416667097,
352.94199999999876: A2^334.55960416667097,
0: C3^334.55960416667097,
0: E3^334.55960416667097,
352.94200000001297: A3^334.55960416667097,
352.94199999999876: F2^334.55960416665675,
0: B2^334.55960416665675,
352.94199999999876: F3^334.55960416665675,
176.47099999999227: E2^166.91215416666694,
176.47100000000648: D2^166.91215416666694,
176.47099999999227: C2^166.91215416666694,
176.47100000000648: B1^166.91215416666694,
3520.9420000000023: A1^669.8545041666648,
`,
],
    difficulties: [
      {
        name: "Kantan",
        notes: [[32, 1], [737, 0], [1090, 1], [1443, 0], [2149, 1], [2502, 1], [2855, 0], [3561, 0], [4267, 1], [4973, 0], [5679, 0], [6384, 1], [6737, 1], [7090, 0], [7796, 1], [8149, 0], [8502, 0], [9208, 0], [9561, 1], [9914, 0], [10620, 0], [11326, 1], [12032, 0], [12384, 1], [12737, 0], [13443, 0], [13796, 0], [14149, 1], [14855, 0], [15561, 1], [16267, 0], [16973, 0], [17679, 1], [18032, 0], [18384, 0], [19090, 0], [19443, 1], [19796, 0], [20502, 1], [21208, 0], [21561, 1], [21914, 0], [22620, 1], [23326, 0], [23679, 1], [24032, 0], [24737, 1], [25443, 1], [26149, 1], [26502, 0], [26855, 1], [27561, 1], [27914, 1], [28267, 0], [28973, 0], [29326, 0], [29679, 1], [30384, 0], [31090, 0], [31796, 0], [32149, 0], [32502, 1], [33208, 1], [33561, 0], [33914, 0], [34620, 0], [35326, 0], [36032, 0], [36737, 0], [37090, 0], [37443, 0], [37796, 0], [38149, 0], [38855, 1], [39561, 1], [39914, 1], [40267, 0], [40973, 1], [41326, 0], [41679, 1], [42384, 0], [43090, 1], [43443, 0], [43796, 0], [44679, 1], [45208, 0], [45561, 1], [46090, 0], [46620, 1], [46973, 0], [47326, 0], [48032, 0], [48737, 1], [49090, 1], [49443, 0], [50326, 1], [50855, 0], [51208, 1], [51737, 1], [52267, 0], [52620, 1], [53149, 1], [53679, 0], [54384, 1], [54737, 1], [55090, 1], [55443, 1], [55796, 0], [56149, 0], [56502, 0], [57208, 1], [57914, 1], [58267, 0], [58620, 1], [59326, 0], [59679, 0], [60032, 1], [60737, 0], [61443, 0], [62149, 1], [62855, 0], [63208, 0], [63561, 1], [64267, 1], [64620, 1], [64973, 0], [65679, 0], [66384, 1], [67090, 0], [67443, 0], [67796, 1], [68502, 0], [69208, 1], [69914, 0], [70267, 1], [70620, 0], [71326, 0], [72032, 1], [72737, 0], [73090, 0], [73443, 1]],
      },
      {
        name: "Futsuu",
        notes: [[32, 1], [384, 0], [561, 0], [737, 1], [1090, 0], [1267, 1], [1443, 0], [1796, 0], [1973, 0], [2149, 1], [2502, 1], [2679, 1], [2855, 0], [3208, 0], [3384, 0], [3561, 1], [3737, 0], [3914, 1], [4267, 1], [4443, 1], [4620, 0], [4973, 0], [5679, 0], [5855, 1], [6208, 1], [6384, 0], [6737, 0], [6914, 1], [7090, 0], [7267, 1], [7620, 0], [7796, 1], [8149, 1], [8326, 1], [8502, 0], [8855, 0], [9032, 0], [9208, 1], [9561, 1], [9914, 0], [10267, 1], [10620, 0], [11326, 1], [11679, 0], [11855, 1], [12032, 0], [12208, 0], [12384, 1], [12737, 0], [13090, 1], [13267, 1], [13443, 0], [13796, 1], [13973, 0], [14149, 1], [14502, 0], [14679, 0], [14855, 1], [15032, 0], [15208, 1], [15561, 0], [15914, 1], [16267, 0], [16620, 1], [16973, 0], [17149, 1], [17502, 1], [17679, 0], [18032, 1], [18208, 0], [18384, 0], [18561, 1], [18914, 0], [19090, 1], [19443, 1], [19620, 0], [19796, 0], [20149, 0], [20326, 1], [20502, 0], [20855, 1], [21208, 0], [21561, 1], [21914, 0], [22620, 1], [22973, 1], [23149, 0], [23326, 1], [23679, 0], [23855, 1], [24032, 1], [24384, 1], [24561, 0], [24737, 1], [24914, 1], [25090, 1], [25443, 0], [25796, 1], [26149, 0], [26502, 0], [26855, 1], [27032, 0], [27208, 1], [27384, 0], [27561, 1], [27737, 0], [27914, 1], [28090, 0], [28267, 0], [28620, 1], [28796, 1], [28973, 0], [29326, 1], [29679, 0], [30032, 0], [30208, 1], [30384, 0], [30737, 1], [31090, 0], [31443, 1], [31796, 0], [32149, 0], [32502, 1], [32855, 0], [33208, 1], [33561, 0], [33914, 0], [34267, 0], [34443, 0], [34620, 0], [34973, 0], [35149, 0], [35326, 1], [35679, 0], [35855, 0], [36032, 0], [36384, 0], [36561, 0], [36737, 1], [36914, 0], [37090, 1], [37267, 0], [37443, 1], [37620, 0], [37796, 1], [37973, 0], [38149, 1], [38855, 1], [39208, 0], [39384, 1], [39561, 1], [39737, 0], [39914, 1], [40267, 0], [40620, 1], [40796, 0], [40973, 1], [41326, 1], [41502, 1], [41679, 0], [42032, 1], [42208, 1], [42384, 0], [42561, 0], [42737, 1], [43090, 1], [43443, 0], [43796, 0], [44502, 0], [44679, 1], [45032, 1], [45208, 0], [45561, 0], [45737, 1], [46090, 0], [46443, 0], [46620, 1], [46973, 0], [47149, 0], [47326, 1], [47679, 0], [47855, 1], [48032, 0], [48384, 1], [48737, 0], [49090, 0], [49443, 1], [50149, 0], [50326, 1], [50679, 1], [50855, 0], [51032, 0], [51208, 1], [51561, 0], [51737, 1], [52090, 0], [52267, 1], [52443, 1], [52620, 0], [52973, 0], [53149, 1], [53502, 1], [53679, 1], [53855, 0], [54032, 0], [54384, 1], [54561, 0], [54737, 1], [54914, 0], [55090, 1], [55267, 0], [55443, 1], [55796, 0], [55973, 1], [56149, 0], [56326, 1], [56502, 0], [56855, 1], [57208, 0], [57384, 1], [57561, 0], [57914, 1], [58090, 0], [58267, 1], [58620, 0], [58796, 0], [58973, 1], [59149, 1], [59326, 0], [59679, 1], [60032, 0], [60208, 0], [60384, 0], [60737, 1], [60914, 1], [61090, 1], [61443, 0], [61620, 1], [61796, 0], [61973, 1], [62149, 0], [62502, 1], [62855, 0], [63032, 1], [63208, 0], [63561, 1], [63737, 1], [63914, 0], [64267, 0], [64443, 0], [64620, 1], [64796, 1], [64973, 0], [65326, 0], [65679, 1], [65855, 0], [66032, 1], [66384, 0], [66561, 1], [66737, 0], [67090, 1], [67267, 1], [67443, 0], [67620, 0], [67796, 1], [68149, 0], [68502, 0], [68679, 1], [68855, 0], [69208, 1], [69384, 0], [69561, 1], [69914, 1], [70090, 1], [70267, 1], [70443, 0], [70620, 1], [70973, 0], [71326, 1], [71502, 1], [71679, 0], [72032, 1], [72208, 0], [72384, 0], [72737, 0], [72914, 0], [73090, 1], [73267, 0], [73443, 1]],
      },
      {
        name: "Muzukashii",
        notes: [[32, 0], [384, 1], [561, 0], [649, 0], [737, 0], [914, 0], [1090, 1], [1267, 0], [1443, 0], [1796, 1], [1973, 0], [2149, 0], [2502, 1], [2679, 1], [2855, 0], [3208, 0], [3384, 1], [3561, 0], [3914, 1], [4267, 0], [4443, 0], [4620, 1], [4796, 0], [4973, 0], [5679, 0], [5767, 0], [5855, 1], [6208, 1], [6384, 0], [6737, 1], [6914, 1], [7090, 0], [7179, 0], [7267, 1], [7620, 1], [7796, 0], [8149, 1], [8326, 0], [8502, 0], [8855, 0], [9032, 0], [9208, 1], [9384, 0], [9561, 1], [9914, 0], [10267, 1], [10620, 0], [11326, 1], [11679, 1], [11767, 1], [11855, 0], [12032, 1], [12208, 0], [12384, 0], [12561, 0], [12737, 1], [13090, 0], [13267, 0], [13355, 1], [13443, 1], [13796, 1], [13973, 0], [14149, 0], [14502, 0], [14679, 0], [14855, 1], [15032, 0], [15208, 1], [15561, 1], [15737, 1], [15914, 0], [16267, 0], [16973, 0], [17061, 0], [17149, 1], [17502, 1], [17679, 0], [17855, 0], [18032, 1], [18384, 0], [18473, 0], [18561, 1], [18914, 1], [19090, 0], [19267, 0], [19443, 0], [19620, 1], [19796, 0], [20149, 0], [20326, 0], [20502, 1], [20679, 0], [20855, 1], [21208, 1], [21384, 1], [21561, 0], [21914, 0], [22620, 1], [22973, 0], [23149, 1], [23237, 1], [23326, 1], [23502, 0], [23679, 1], [24032, 0], [24208, 1], [24384, 0], [24561, 0], [24737, 1], [25090, 1], [25443, 0], [25796, 0], [26149, 1], [26502, 0], [26855, 1], [27032, 1], [27208, 0], [27384, 0], [27561, 1], [27737, 1], [27914, 0], [28090, 0], [28267, 1], [28620, 0], [28796, 1], [28973, 0], [29326, 1], [29679, 0], [30032, 1], [30208, 1], [30384, 0], [30737, 1], [31090, 0], [31443, 0], [31620, 0], [31796, 0], [32149, 1], [32502, 0], [32855, 1], [33032, 0], [33208, 0], [33561, 1], [33914, 0], [34090, 0], [34267, 0], [34443, 1], [34620, 0], [35326, 0], [35502, 0], [35679, 0], [35855, 1], [36032, 0], [36737, 0], [36914, 0], [37090, 0], [37267, 1], [37443, 0], [37620, 0], [37796, 0], [37973, 1], [38149, 1], [38855, 0], [39208, 1], [39296, 0], [39384, 0], [39561, 1], [39737, 0], [39914, 0], [40090, 1], [40267, 0], [40620, 1], [40796, 1], [40973, 0], [41326, 0], [41502, 1], [41679, 0], [42032, 0], [42208, 0], [42384, 1], [42561, 1], [42737, 0], [43090, 1], [43267, 1], [43443, 0], [43796, 0], [44502, 1], [44590, 0], [44679, 0], [45032, 0], [45208, 1], [45561, 1], [45914, 1], [46002, 1], [46090, 0], [46443, 0], [46620, 1], [46973, 1], [47149, 0], [47326, 0], [47679, 0], [47855, 0], [48032, 1], [48208, 1], [48384, 0], [48737, 1], [48914, 0], [49090, 1], [49443, 0], [50149, 0], [50326, 1], [50679, 0], [50855, 1], [51208, 1], [51561, 0], [51737, 1], [52090, 0], [52267, 1], [52620, 1], [52973, 0], [53149, 1], [53502, 0], [53679, 1], [54032, 1], [54384, 1], [54561, 1], [54737, 1], [54914, 1], [55090, 1], [55267, 1], [55443, 1], [55796, 0], [55973, 0], [56149, 0], [56326, 0], [56502, 0], [57208, 1], [57384, 0], [57561, 1], [57914, 0], [58090, 0], [58267, 1], [58620, 0], [58796, 1], [58973, 1], [59149, 0], [59326, 1], [59502, 0], [59679, 1], [60032, 1], [60120, 1], [60208, 1], [60384, 0], [60473, 0], [60561, 0], [60737, 1], [60826, 1], [60914, 1], [61090, 0], [61179, 0], [61267, 0], [61443, 1], [61796, 1], [62149, 0], [62502, 1], [62855, 0], [63032, 0], [63208, 1], [63561, 0], [63914, 1], [64267, 0], [64443, 0], [64620, 1], [64796, 1], [64973, 0], [65679, 0], [66032, 1], [66384, 0], [66561, 0], [66737, 1], [67090, 0], [67267, 0], [67443, 1], [67620, 1], [67796, 0], [67973, 0], [68149, 1], [68502, 1], [68590, 0], [68679, 1], [68855, 0], [68943, 1], [69032, 0], [69208, 1], [69296, 0], [69384, 1], [69561, 0], [69649, 1], [69737, 0], [69914, 1], [70267, 1], [70620, 0], [70973, 1], [71326, 0], [71502, 0], [71679, 1], [72032, 0], [72384, 1], [72737, 1], [72826, 0], [72914, 1], [73090, 1], [73179, 0], [73267, 0], [73443, 1]],
      },
      {
        name: "Oni",
        notes: [[32, 1], [384, 0], [561, 0], [737, 1], [914, 1], [1002, 1], [1090, 0], [1267, 1], [1443, 0], [1620, 0], [1796, 1], [1973, 0], [2149, 1], [2326, 0], [2414, 0], [2502, 1], [2590, 1], [2679, 0], [2855, 1], [3032, 0], [3120, 1], [3208, 1], [3384, 0], [3561, 1], [3737, 0], [3914, 1], [4090, 1], [4267, 0], [4443, 1], [4620, 1], [4796, 1], [4973, 0], [5679, 0], [5855, 0], [5943, 0], [6032, 1], [6208, 1], [6384, 0], [6561, 1], [6649, 1], [6737, 0], [6914, 1], [7090, 0], [7267, 0], [7355, 0], [7443, 1], [7620, 1], [7796, 0], [7973, 1], [8061, 1], [8149, 0], [8326, 1], [8502, 0], [8679, 0], [8855, 0], [9032, 1], [9208, 0], [9561, 1], [9914, 0], [10267, 1], [10620, 0], [11326, 1], [11679, 0], [11855, 1], [12032, 0], [12208, 1], [12296, 1], [12384, 0], [12473, 0], [12561, 1], [12737, 0], [12914, 0], [13002, 1], [13090, 1], [13267, 0], [13443, 1], [13620, 1], [13708, 0], [13796, 0], [13973, 1], [14149, 0], [14326, 0], [14502, 0], [14679, 0], [14767, 0], [14855, 1], [15032, 0], [15208, 1], [15384, 0], [15561, 1], [15737, 1], [15826, 0], [15914, 1], [16090, 1], [16267, 0], [16620, 0], [16796, 0], [16973, 0], [17149, 1], [17326, 1], [17502, 0], [17679, 1], [17855, 1], [17943, 1], [18032, 0], [18208, 1], [18384, 0], [18561, 1], [18737, 1], [18914, 0], [19090, 1], [19267, 1], [19355, 1], [19443, 0], [19620, 1], [19796, 0], [19973, 0], [20149, 0], [20326, 1], [20502, 0], [20855, 1], [21208, 0], [21561, 0], [21914, 1], [22620, 1], [22796, 1], [22973, 0], [23149, 1], [23326, 0], [23502, 0], [23679, 1], [23855, 0], [24032, 0], [24208, 0], [24384, 1], [24561, 0], [24737, 1], [24914, 0], [25090, 1], [25267, 1], [25443, 0], [25620, 1], [25796, 0], [25973, 0], [26149, 1], [26326, 1], [26502, 0], [26679, 0], [26855, 1], [26943, 1], [27032, 1], [27208, 1], [27296, 1], [27384, 0], [27561, 1], [27649, 0], [27737, 0], [27914, 0], [28002, 0], [28090, 0], [28267, 0], [28443, 0], [28620, 0], [28796, 1], [28973, 0], [29149, 0], [29326, 1], [29502, 1], [29679, 0], [29855, 0], [30032, 0], [30208, 1], [30384, 0], [30561, 0], [30737, 1], [30914, 0], [31090, 0], [31443, 0], [31796, 1], [32149, 1], [32502, 0], [32855, 0], [33208, 1], [33561, 1], [33914, 0], [34090, 1], [34267, 0], [34443, 1], [34620, 0], [34796, 1], [34973, 0], [35149, 0], [35326, 0], [35502, 1], [35679, 0], [35855, 1], [36032, 0], [36208, 1], [36384, 1], [36561, 0], [36737, 0], [36914, 1], [37090, 0], [37267, 1], [37443, 0], [37620, 1], [37796, 1], [37973, 0], [38149, 1], [38855, 1], [39032, 0], [39120, 0], [39208, 1], [39296, 1], [39384, 0], [39561, 1], [39737, 1], [39826, 1], [39914, 0], [40090, 1], [40267, 0], [40443, 0], [40620, 1], [40708, 0], [40796, 1], [40884, 1], [40973, 0], [41149, 1], [41237, 0], [41326, 0], [41502, 1], [41679, 0], [41855, 0], [41943, 1], [42032, 0], [42208, 1], [42384, 0], [42561, 0], [42737, 1], [42914, 1], [43090, 0], [43267, 0], [43355, 1], [43443, 0], [43532, 1], [43620, 1], [43796, 0], [44502, 0], [44679, 1], [44855, 1], [44943, 1], [45032, 0], [45208, 1], [45384, 0], [45473, 1], [45561, 0], [45737, 1], [45914, 0], [46090, 1], [46267, 1], [46443, 0], [46620, 1], [46796, 0], [46884, 0], [46973, 1], [47061, 0], [47149, 1], [47326, 0], [47679, 1], [47855, 1], [48032, 0], [48384, 1], [48737, 0], [49090, 1], [49443, 0], [50149, 0], [50326, 1], [50502, 1], [50590, 1], [50679, 1], [50855, 0], [51208, 1], [51561, 0], [51737, 1], [51914, 0], [52002, 1], [52090, 1], [52267, 0], [52620, 1], [52973, 0], [53149, 1], [53326, 1], [53414, 1], [53502, 0], [53679, 0], [54032, 1], [54384, 0], [54561, 0], [54737, 0], [54914, 1], [55090, 0], [55267, 0], [55443, 1], [55532, 1], [55620, 1], [55796, 0], [55973, 1], [56149, 0], [56237, 1], [56326, 1], [56502, 0], [56855, 1], [57208, 0], [57296, 0], [57384, 0], [57561, 1], [57737, 0], [57914, 0], [58090, 1], [58179, 0], [58267, 0], [58443, 1], [58620, 0], [58796, 0], [58884, 0], [58973, 1], [59061, 1], [59149, 0], [59326, 0], [59502, 1], [59590, 1], [59679, 0], [59855, 1], [60032, 0], [60120, 0], [60208, 1], [60296, 1], [60384, 0], [60473, 0], [60561, 1], [60649, 1], [60737, 0], [60826, 0], [60914, 1], [61002, 1], [61090, 0], [61179, 0], [61267, 1], [61355, 1], [61443, 0], [61620, 0], [61796, 1], [61884, 1], [61973, 0], [62149, 0], [62237, 0], [62326, 1], [62414, 0], [62502, 1], [62679, 0], [62855, 1], [63032, 0], [63120, 0], [63208, 1], [63384, 0], [63561, 1], [63737, 1], [63914, 1], [64090, 0], [64267, 1], [64443, 1], [64620, 0], [64796, 0], [64973, 1], [65326, 0], [65679, 1], [65855, 1], [65943, 0], [66032, 1], [66208, 0], [66384, 1], [66561, 0], [66649, 0], [66737, 1], [66914, 0], [67090, 1], [67267, 1], [67355, 1], [67443, 0], [67620, 1], [67796, 0], [67973, 1], [68149, 1], [68326, 0], [68502, 1], [68590, 0], [68679, 0], [68767, 0], [68855, 1], [68943, 0], [69032, 0], [69120, 0], [69208, 1], [69296, 0], [69384, 0], [69473, 0], [69561, 1], [69649, 0], [69737, 0], [69826, 0], [69914, 1], [70090, 1], [70179, 0], [70267, 1], [70443, 0], [70620, 1], [70796, 1], [70884, 1], [70973, 0], [71149, 1], [71326, 0], [71502, 0], [71679, 1], [71855, 1], [72032, 0], [72120, 0], [72208, 1], [72296, 1], [72384, 0], [72561, 1], [72737, 0], [72914, 0], [73090, 1], [73267, 1], [73443, 0]],
      },
    ],
  },
];
 
/**************************
 *         Sounds         *
 **************************/

// On a taiko drum, you can either hit the drum for a DON sound,
// or hit the rim for a KAT sound.

// red notes
const hitsoundDon = tune`0: C4/250`;

// blue notes
const hitsoundKat = tune`0: D4-250`;

// A music player based on https://the.lastgimbus.com/midi-to-sprig
// It's an implementation of playTune() that supports multiple, simultaneous tracks.
class Music {
  constructor() {
    this.tracks = [];
    this.player = null;
    this.start = null;
    this.count = 0;

    setInterval(() => {
      if (this.player === null) {
        return;
      }

      // Stop playing once the counter reaches 0
      if (this.count == 0) {
        this.stop();
      }

      // Decrement the counter once all tracks finish playing
      if (this.tracks.every((track) => track[0] === -1)) {
        for (const track of this.tracks) {
          track[0] = 0;
        }
        this.count--;

        // Restart playback
        this.start = Date.now();
      }
    });
  }

  stop() {
    clearInterval(this.player);
    this.player = null;
    this.count = 0;
  }

  load(tracks) {
    this.tracks = [];
  
    // Loop through each track and create a new track that uses timestamps instead of delays
    for (const track of tracks) {
      const newTrack = [];

      // Sum of the times
      let time = 0;

      // Create the track
      for (const line of track.trim().split('\n')) {
        // Find the delay and note of the current line
        const [delayString, note] = line.replaceAll(',', '').split(': ');

        // Add the note to the track
        if (note) {
          newTrack.push([
            time,
            '0: ' + note
          ]);
        }

        // Add the delay to the total time
        time += Number(delayString);
      }

      // Join notes on the same time
      let index = 0;
      while (index < newTrack.length - 2) {
        const trackA = newTrack[index];
        const trackB = newTrack[index + 1];

        if (trackA[0] === trackB[0]) {
          trackA[1] += ',\n' + trackB[1];
          newTrack.splice(index + 1, 1);
          continue;
        }

        index++;
      }

      // [track pointer, track]
      this.tracks.push([0, newTrack]);
    }
  }

  play(count = 1) {
    // Stop what's currently playing
    this.stop();

    // Set the counter
    this.count = count;

    // Reset track indexes
    for (const track of this.tracks) {
      track[0] = 0;
    }

    // Play the tune
    this.start = Date.now();
    this.player = setInterval(() => {
      const time = Date.now() - this.start;

      // Loop through each track individually
      for (const track of this.tracks) {
        let index = track[0];
        const notes = track[1];

        // Stop playing the track once it has finished
        if (index === -1) {
          continue;
        }

        // The track has its own pointer for its position in the track
        for (; index < notes.length; index++) {
          const [noteTime, note] = notes[index];

          // Stop once we reach a note that shouldn't be played yet
          if (noteTime > time) {
            break;
          }

          // Otherwise play the note
          playTune(note);
        }

        // Update the track index
        track[0] = index == notes.length
          // If the track has finished, set the pointer to a special value to mark it as completed
          ? -1
          // Otherwise update the index normally
          : index;
      }
    });
  }
}

/**************************
 * Structure declarations *
 **************************/

// The game state is managed by a stack. 

class GlobalState {
  constructor() {
    this.states = [];
  }

  current() {
    return this.states[this.states.length - 1];
  }

  peekBack() {
    return this.states[this.states.length - 2];
  }

  pop() {
    this.states.pop();
  }

  push(state) {
    this.states.push(state);
  }
}

class SelectBeatmapState {
  constructor() {
    this.selection = 0;
    this.prevSelection = null;
  }

  prevBeatmap() {
    if (this.selection <= 0)
      return;
    
    this.selection--;
  }

  nextBeatmap() {
    if (this.selection >= BEATMAPS.length - 1)
      return;
    
    this.selection++;
  }

  getSelectedBeatmap() {
    return BEATMAPS[this.selection];
  }
}

class SelectDifficultyState {
  constructor(beatmap) {
    this.beatmap = beatmap;
    this.selection = 0;
  }

  prevDifficulty() {
    if (this.selection <= 0)
      return;
    
    this.selection--;
  }

  nextDifficulty() {
    if (this.selection >= this.beatmap.difficulties.length - 1)
      return;
    
    this.selection++;
  }

  getSelectedDifficulty() {
    return this.beatmap.difficulties[this.selection];
  }
}

class GameState {
  constructor(beatmapDifficulty) {
    this.beatmapDifficulty = beatmapDifficulty;
    this.score = 0;
    this.accuracies = [];
    this.maxCombo = 0;
    this.combo = 0;
  }

  calculateAccuracy() {
    // The default is 100 if no hits have been made yet
    if (this.accuracies.length == 0) {
      return 100;
    }

    // The accuracy is the current score divided by the perfect score for the given moment
    // Round to 2 decimal places
    return Math.round(this.accuracies.reduce((a, b) => a + b) / (this.accuracies.length * 100) * 100 * 100) / 100;
  }
}

class ResultState {
  constructor({ score, combo, accuracy, letterScore, hitCount, songTitle, diffTitle }) {
    this.score = score;
    this.combo = combo;
    this.accuracy = accuracy;
    this.letterScore = letterScore;
    this.hitCount = hitCount;
    this.songTitle = songTitle;
    this.diffTitle = diffTitle;
  }
}

/**************************
 *      Input events      *
 **************************/

// Handling inputs in this manner allows us to dynamically
// change the callback for each input event.

const inputEvents = {
  w: () => {},
  a: () => {},
  s: () => {},
  d: () => {},

  i: () => {},
  j: () => {},
  k: () => {},
  l: () => {},
};

// These onInput events use the modifiable functions above
onInput("w", () => inputEvents.w());
onInput("a", () => inputEvents.a());
onInput("s", () => inputEvents.s());
onInput("d", () => inputEvents.d());

onInput("i", () => inputEvents.i());
onInput("j", () => inputEvents.j());
onInput("k", () => inputEvents.k());
onInput("l", () => inputEvents.l());

// Store a copy of the empty inputEvents object to easily
// reset the original one later.
const _clearedInputEvents = Object.fromEntries(Object.entries(inputEvents));
Object.freeze(_clearedInputEvents);

function clearInputEvents() {
  Object.assign(inputEvents, _clearedInputEvents);
}

/**************************
 *         Screens        *
 **************************/
function gotoSelectBeatmapScreen() {
  globalState.push(new SelectBeatmapState());
  drawSelectBeatmapScreen();
}

function gotoSelectDifficultyScreen(beatmap) {
  globalState.push(new SelectDifficultyState(beatmap));
  drawSelectDifficultyScreen();
}

function gotoGameScreen(difficulty) {
  globalState.push(new GameState(difficulty));
  drawGameScreen();
}

function gotoResultsScreen() {
  const gameState = globalState.current();
  const selectState = globalState.peekBack();

  const accuracy = gameState.calculateAccuracy();

  globalState.pop();
  globalState.push(new ResultState({
    score: gameState.score,
    combo: gameState.maxCombo,
    accuracy,
    letterScore: accuracy >= 95
      ? "S"
      : accuracy >= 90
        ? "A"
        : accuracy >= 80
          ? "B"
          : accuracy >= 70
            ? "C"
            : "F",
    hitCount: {
      great: gameState.accuracies.filter((acc) => acc === 100).length,
      ok: gameState.accuracies.filter((acc) => acc === 50).length,
      miss: gameState.accuracies.filter((acc) => acc === 0).length,
    },
    songTitle: selectState.beatmap.title,
    diffTitle: gameState.beatmapDifficulty.name,
  }));

  drawResultsScreen();
}

function drawTitleScreen() {
  //
  // Secret "drum mode"
  //

  const UP = 0;
  const DOWN = 1;
  const LEFT = 2;
  const RIGHT = 3;

  // History of all inputs
  const inputHistory = [];
  
  const konamiCode = [UP, UP, DOWN, DOWN, LEFT, RIGHT, LEFT, RIGHT];
  let drumMode = false;

  const tuneDrumModeOn = tune`
250,
150: C5-250,
150: G5-250,`;
  const tuneDrumModeOff = tune`
250,
150: G5-250,
150: C5-250,`;
  
  const updateKonami = (key) => {
    inputHistory.push(key);

    // Check if the last 8 inputs match the code
    if (
      inputHistory.length >= konamiCode.length &&
      inputHistory.slice(inputHistory.length - konamiCode.length).every(
       (key, i) => key == konamiCode[i]
      )
    ) {
      drumMode = !drumMode;

      // Add indicators of drum mode
      clearText();
      addText(drumMode ? "taiko\nfever\nmode" : "taiko\nfever", {
        x: 7,
        y: 4,
        color: drumMode ? color`3` : color`L`
      });
      addText("a              l", {x: 2, y: 10, color: color`7`});
      addText("  d        j", {x: 3, y: 10, color: color`3`});
      addText("(BASH TO CONTINUE)", {x: 1, y: 12, color: color`L`});
      playTune(drumMode ? tuneDrumModeOn : tuneDrumModeOff);

      // Reset active keys
      activeKeys[0] = false;
      activeKeys[1] = false;
      activeKeys[2] = false;
      activeKeys[3] = false;

      for (const key of "ABCDWXYZ") {
        getAll(key).map((sprite) => { sprite.type = sprite.type.toLowerCase() });
      }
    }
  }

  //
  // Back to the regular code
  //
  
  // draw the keypads
  setLegend(
    ['a', bitmap`
..........111111
..........111111
..........111111
..........111111
..........111111
..........111111
..........111111
..........111111
..........111111
..........111111
1111111111......
1111111111......
11LLLLLL11......
11LLLLLL11......
11LLLLLL11......
11LLLLLL11......`],
    ['b', bitmap`
1111............
1111............
1111............
1111............
1111............
1111............
1111............
1111............
1111............
1111............
....1111111111..
....1111111111..
....11LLLLLL11..
....11LLLLLL11..
....11LLLLLL11..
....11LLLLLL11..`],
    ['c', bitmap`
11LLLLLL11......
11LLLLLL11......
1111111111......
1111111111......
..........111111
..........111111
..........111111
..........111111
..........111111
..........111111
..........111111
..........111111
..........111111
..........111111
................
................`],
    ['d', bitmap`
....11LLLLLL11..
....11LLLLLL11..
....1111111111..
....1111111111..
1111............
1111............
1111............
1111............
1111............
1111............
1111............
1111............
1111............
1111............
................
................`],
    ['w', bitmap`
............1111
............1111
............1111
............1111
............1111
............1111
............1111
............1111
............1111
............1111
..1111111111....
..1111111111....
..11LLLLLL11....
..11LLLLLL11....
..11LLLLLL11....
..11LLLLLL11....`],
    ['x', bitmap`
111111..........
111111..........
111111..........
111111..........
111111..........
111111..........
111111..........
111111..........
111111..........
111111..........
......1111111111
......1111111111
......11LLLLLL11
......11LLLLLL11
......11LLLLLL11
......11LLLLLL11`],
    ['y', bitmap`
..11LLLLLL11....
..11LLLLLL11....
..1111111111....
..1111111111....
............1111
............1111
............1111
............1111
............1111
............1111
............1111
............1111
............1111
............1111
................
................`],
    ['z', bitmap`
......11LLLLLL11
......11LLLLLL11
......1111111111
......1111111111
111111..........
111111..........
111111..........
111111..........
111111..........
111111..........
111111..........
111111..........
111111..........
111111..........
................
................`],
    
    ['A', bitmap`
..........111111
..........111111
..........111111
..........111111
..........111111
..........111111
..........111111
..........111111
..........111111
..........111111
1111111111......
1111111111......
1177777711......
1177777711......
1177777711......
1177777711......`],
    ['B', bitmap`
1111............
1111............
1111............
1111............
1111............
1111............
1111............
1111............
1111............
1111............
....1111111111..
....1111111111..
....1133333311..
....1133333311..
....1133333311..
....1133333311..`],
    ['C', bitmap`
1177777711......
1177777711......
1111111111......
1111111111......
..........111111
..........111111
..........111111
..........111111
..........111111
..........111111
..........111111
..........111111
..........111111
..........111111
................
................`],
    ['D', bitmap`
....1133333311..
....1133333311..
....1111111111..
....1111111111..
1111............
1111............
1111............
1111............
1111............
1111............
1111............
1111............
1111............
1111............
................
................`],
    ['W', bitmap`
............1111
............1111
............1111
............1111
............1111
............1111
............1111
............1111
............1111
............1111
..1111111111....
..1111111111....
..1133333311....
..1133333311....
..1133333311....
..1133333311....`],
    ['X', bitmap`
111111..........
111111..........
111111..........
111111..........
111111..........
111111..........
111111..........
111111..........
111111..........
111111..........
......1111111111
......1111111111
......1177777711
......1177777711
......1177777711
......1177777711`],
    ['Y', bitmap`
..1133333311....
..1133333311....
..1111111111....
..1111111111....
............1111
............1111
............1111
............1111
............1111
............1111
............1111
............1111
............1111
............1111
................
................`],
    ['Z', bitmap`
......1177777711
......1177777711
......1111111111
......1111111111
111111..........
111111..........
111111..........
111111..........
111111..........
111111..........
111111..........
111111..........
111111..........
111111..........
................
................`],

    ['#', bitmap`
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL`],
  ); 
  setMap(map`
  ##########
  ..........
  ..........
  .ab....wx.
  .cd....yz.
  ..........
  ..........
  ##########`);

  // draw text
  addText("taiko\nfever", {x: 7, y: 4, color: color`L`});
  addText("a              l", {x: 2, y: 10, color: color`7`});
  addText("  d        j", {x: 3, y: 10, color: color`3`});
  addText("(BASH TO CONTINUE)", {x: 1, y: 12, color: color`L`});
  
  // History of which game keys have been pressed
  const activeKeys = [false, false, false, false];

  function activateKeypad(keyA, keyB, index) {
    // Don't update activeKeys state if in drum mode
    if (drumMode) {
      return;
    }

    // Change keypad sprite
    getAll(keyA).map((sprite) => { sprite.type = sprite.type.toUpperCase() });
    getAll(keyB).map((sprite) => { sprite.type = sprite.type.toUpperCase() });
    
    // Set to true in activeKeys
    activeKeys[index] = true;

    // Go to menu select screen once all keys are pressed
    if (activeKeys.every((bool) => bool)) {
      // Clean up the screen
      clearText();
      clearInputEvents();

      gotoSelectBeatmapScreen();
    }
  };
  
  inputEvents.a = () => {
    updateKonami(LEFT);

    activateKeypad("a", "c", 0);
    playTune(hitsoundKat);
  };

  inputEvents.d = () => {
    updateKonami(RIGHT);
    
    activateKeypad("b", "d", 1);
    playTune(hitsoundDon);
  };

  inputEvents.j = () => {
    updateKonami(LEFT);
    
    activateKeypad("w", "y", 2);
    playTune(hitsoundDon);
  };

  inputEvents.l = () => {
    updateKonami(RIGHT);
    
    activateKeypad("x", "z", 3);
    playTune(hitsoundKat);
  };

  inputEvents.w = () => { updateKonami(UP); }
  inputEvents.i = () => { updateKonami(UP); }
  inputEvents.s = () => { updateKonami(DOWN); }
  inputEvents.k = () => { updateKonami(DOWN); }
}

function drawSelectBeatmapScreen() {
  let state = globalState.current();
  
  // draw the menu
  const arrowUp = bitmap`
................
.......LL.......
.......LL.......
......LLLL......
......LLLL......
.....LLLLLL.....
.....LLLLLL.....
....LLLLLLLL....
....LLLLLLLL....
...LLLLLLLLLL...
.......LL.......
.......LL.......
.......LL.......
.......LL.......
.......LL.......
................`;
  const arrowDown = arrowUp.split('\n').reverse().join('\n');
  const keypadUp = bitmap`
................
................
......LLLL......
......LLLL......
......LLLL......
......LLLL......
..1111....1111..
..1111....1111..
..1111....1111..
..1111....1111..
......1111......
......1111......
......1111......
......1111......
................
................`;
  const keypadDown = keypadUp.split('\n').reverse().join('\n');
  const keypadRight = bitmap`
................
................
......1111......
......1111......
......1111......
......1111......
..1111....LLLL..
..1111....LLLL..
..1111....LLLL..
..1111....LLLL..
......1111......
......1111......
......1111......
......1111......
................
................`;
  
  setLegend(
    ['U', arrowUp],
    ['u', keypadUp],
    ['D', arrowDown],
    ['d', keypadDown],
    ['O', bitmap`
................
.LLLLLL.LL...LL.
.LLLLLL.LL...LL.
.LL..LL.LL.LL...
.LL..LL.LL.LL...
.LL..LL.LL.LL...
.LL..LL.LLL.....
.LL..LL.LLL.....
.LL..LL.LLL.....
.LL..LL.LLL.....
.LL..LL.LL.LL...
.LL..LL.LL.LL...
.LL..LL.LL.LL...
.LLLLLL.LL...LL.
.LLLLLL.LL...LL.
................`],
    ['K', keypadRight],
  );
  
  setMap(`
  ....Uu....
  ..........
  ..........
  ........OK
  ..........
  ..........
  ..........
  ....Dd....`);

  // display beatmap listing
  function drawBeatmapListing() {
    // prevent replaying preview music when the beatmap hasn't changed
    if (state.prevSelection != state.selection) {
      state.prevSelection = state.selection;

      // Stop playing music and play the selected beatmap
      music.load(state.getSelectedBeatmap().music);
      music.play(Infinity);
    }

    // Clear text before adding new ones
    clearText();

    if (BEATMAPS.length == 0) {
      addText("No beatmaps :(", {
        x: posX,
        y: centerY,
        color: color`0`,
      });
      return;
    }

    // Baseline for the text
    const posX = BEATMAPS.length > 1 ? 1 : -1;
    const centerY = 7;

    // 
    //   ...
    //   previous
    //   current
    //   next
    //   ...
    if (state.selection >= 2)
      addText("...", {
        x: posX,
        y: centerY - 4,
        color: color`0`
      });

    if (state.selection >= 1)
      addText(BEATMAPS[state.selection - 1].title, {
        x: posX,
        y: centerY - 2,
        color: color`0`
      });

    addText(state.getSelectedBeatmap().title, {
      x: posX + 2,
      y: centerY,
      color: color`0`
    });

    if (BEATMAPS.length - state.selection >= 2)
      addText(BEATMAPS[state.selection + 1].title, {
        x: posX,
        y: centerY + 2,
        color: color`0`
      });

    if (BEATMAPS.length - state.selection >= 3)
      addText("...", {
        x: posX,
        y: centerY + 4,
        color: color`0`
      });
  }

  const nextBeatmap = () => {
    state.nextBeatmap();
    playTune(hitsoundDon);
    drawBeatmapListing();
  };
  const prevBeatmap = () => {
    state.prevBeatmap();
    playTune(hitsoundDon);
    drawBeatmapListing();
  };
  const gotoSelect = () => {
    clearText();
    clearInputEvents();
    
    playTune(hitsoundDon);

    gotoSelectDifficultyScreen(state.getSelectedBeatmap());
  }
  
  inputEvents.w = prevBeatmap;
  inputEvents.i = prevBeatmap;
  inputEvents.s = nextBeatmap;
  inputEvents.k = nextBeatmap;

  inputEvents.d = gotoSelect;
  inputEvents.l = gotoSelect;

  drawBeatmapListing();
}

function drawSelectDifficultyScreen() {
  let state = globalState.current();
  
  // draw the menu
  const arrowUp = bitmap`
................
.......LL.......
.......LL.......
......LLLL......
......LLLL......
.....LLLLLL.....
.....LLLLLL.....
....LLLLLLLL....
....LLLLLLLL....
...LLLLLLLLLL...
.......LL.......
.......LL.......
.......LL.......
.......LL.......
.......LL.......
................`;
  const arrowDown = arrowUp.split('\n').reverse().join('\n');
  const keypadUp = bitmap`
................
................
......LLLL......
......LLLL......
......LLLL......
......LLLL......
..1111....1111..
..1111....1111..
..1111....1111..
..1111....1111..
......1111......
......1111......
......1111......
......1111......
................
................`;
  const keypadDown = keypadUp.split('\n').reverse().join('\n');
  const keypadRight = bitmap`
................
................
......1111......
......1111......
......1111......
......1111......
..1111....LLLL..
..1111....LLLL..
..1111....LLLL..
..1111....LLLL..
......1111......
......1111......
......1111......
......1111......
................
................`;
  const keypadLeft = keypadRight.split('\n').map(line => line.split('').reverse().join('')).join('\n');
  
  setLegend(
    ['U', arrowUp],
    ['u', keypadUp],
    ['D', arrowDown],
    ['d', keypadDown],
    
    ['O', bitmap`
................
.LLLLLL.LL...LL.
.LLLLLL.LL...LL.
.LL..LL.LL.LL...
.LL..LL.LL.LL...
.LL..LL.LL.LL...
.LL..LL.LLL.....
.LL..LL.LLL.....
.LL..LL.LLL.....
.LL..LL.LLL.....
.LL..LL.LL.LL...
.LL..LL.LL.LL...
.LL..LL.LL.LL...
.LLLLLL.LL...LL.
.LLLLLL.LL...LL.
................`],
    ['K', keypadRight],
    
    ['B', bitmap`
................
..........LLLL..
..........LLLL..
..........LL..LL
..........LL..LL
..........LL..LL
..........LL..LL
..........LLLL..
..........LLLL..
..........LL..LL
..........LL..LL
..........LL..LL
..........LL..LL
..........LLLL..
..........LLLL..
................`],
    ['a', bitmap`
................
.LLLLLL.LL...LL.
.LLLLLL.LL...LL.
.LL..LL.LL.LL...
.LL..LL.LL.LL...
.LL..LL.LL.LL...
.LL..LL.LLL.....
.LLLLLL.LLL.....
.LLLLLL.LLL.....
.LL..LL.LLL.....
.LL..LL.LL.LL...
.LL..LL.LL.LL...
.LL..LL.LL.LL...
.LL..LL.LL...LL.
.LL..LL.LL...LL.
................`],
    ['k', keypadLeft],
  );
  
  setMap(`
  ....Uu.Bak
  ..........
  ..........
  ........OK
  ..........
  ..........
  ..........
  ....Dd....`);
  setBackground('');

  // display difficulties
  function drawDifficulties() {
    // Clear text before adding new ones
    clearText();

    const posX = 1;
    const centerY = 7;

    if (BEATMAPS.length == 0) {
      addText("(IT'S EMPTY)", {
        x: posX,
        y: centerY,
        color: color`0`,
      });
      return;
    }

    // 
    //   ...
    //   previous
    //   current
    //   next
    //   ...
    if (state.selection >= 2)
      addText("...", {
        x: posX,
        y: centerY - 4,
        color: color`0`
      });

    const difficulties = state.beatmap.difficulties;

    if (state.selection >= 1)
      addText(difficulties[state.selection - 1].name, {
        x: posX,
        y: centerY - 2,
        color: color`0`
      });

    addText(state.getSelectedDifficulty().name, {
      x: posX + 2,
      y: centerY,
      color: color`0`
    });

    if (difficulties.length - state.selection >= 2)
      addText(difficulties[state.selection + 1].name, {
        x: posX,
        y: centerY + 2,
        color: color`0`
      });

    if (difficulties.length - state.selection >= 3)
      addText("...", {
        x: posX,
        y: centerY + 4,
        color: color`0`
      });
  }

  const nextDifficulty = () => {
    state.nextDifficulty();
    playTune(hitsoundDon);
    drawDifficulties();
  };
  const prevDifficulty = () => {
    state.prevDifficulty();
    playTune(hitsoundDon);
    drawDifficulties();
  };
  const returnToMenu = () => {
    clearText();
    clearInputEvents();

    globalState.pop();
    playTune(hitsoundKat);

    drawSelectBeatmapScreen();
  }
  const playGame = () => {
    clearText();
    clearInputEvents();
    
    playTune(hitsoundDon);

    // Stop preview music
    music.stop();

    gotoGameScreen(state.getSelectedDifficulty());
  }
  
  inputEvents.w = prevDifficulty;
  inputEvents.i = prevDifficulty;
  inputEvents.s = nextDifficulty;
  inputEvents.k = nextDifficulty;

  inputEvents.a = returnToMenu;
  inputEvents.j = returnToMenu;

  inputEvents.d = playGame;
  inputEvents.l = playGame;

  drawDifficulties();
}

function drawGameScreen() {
  let state = globalState.current();

  // Template sprites
  const noteSprite = bitmap`
.....000000.....
...0001111000...
..001111111100..
.00111111111100.
.01111111111110.
0011111111111100
0111111111111110
0111111111111110
0111111111111110
0111111111111110
0011111111111100
.01111111111110.
.00111111111100.
..001111111100..
...0001111000...
.....000000.....`.trim().split('\n');
  const drumSprite = bitmap`
.....000000.....
...0011144400...
..011111444440..
.01111000044440.
.01110223304440.
0111022233304440
0110222233330440
0110222233330440
0110222233330440
0110222233330440
0111022233304440
.01110223304440.
.01111000044440.
..011111444440..
...0011144400...
.....000000.....`;
  {
//   const drumSprite = bitmap`
// 00000000000000..
// 000000000000000.
// 0000000000000000
// 0011022033044000
// 0011022033044000
// 0011022033044000
// 0011022033044000
// 0011022033044000
// 0011022033044000
// 0011022033044000
// 0011022033044000
// 0011022033044000
// 0011022033044000
// 0000000000000000
// 000000000000000.
// 00000000000000..`;
  }

  // [[note, transition note], ...]
  const redNotes = [];
  const blueNotes = [];

  // Generate smooth note sprites
  const generateNoteSprites = (notes, color) => {
    for (let i = 0; i < 16; i++) {
      const mainSprite  = [];
      const transitionSprite = [];
      
      for (const line of noteSprite) {
        const colorLine = line.replaceAll('1', color);

        const paddingLeft = '.'.repeat(i);
        const paddingRight = '.'.repeat(16 /*pixels*/ - i);

        mainSprite.push(colorLine.slice(i) + paddingLeft);
        transitionSprite.push(paddingRight + colorLine.slice(0, i));
      }

      notes.push([mainSprite.join('\n'), transitionSprite.join('\n')]);
    }
  };
  generateNoteSprites(redNotes, color`3`);
  generateNoteSprites(blueNotes, color`7`);

  // Show statistics on screen
  const updateStateText = () => {
    clearText();
    addText('' + state.score, {x: 0, y: 0, color: color`2`});
    addText(state.calculateAccuracy() + '%', {x: 0, y: 1, color: color`2`});
    addText(state.combo + "x", {x: 0, y: 15, color: color`2`});
  }

  // Sprites that remain constant
  const legendKeys = [
    // ...drumLegend,
    ['\x09', noteSprite.join('\n')],

    ['A', bitmap`
................
................
................
................
................
................
................
................
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111`],
    ['B', bitmap`
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111`],
    ['C', bitmap`
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
................
................
................
................
................
................
................
................`],
    ['D', bitmap`
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL`],

  ];
  setLegend(
    ...legendKeys,
  );

  const map = `
  ..........
  AAAAAAAAAA
  BBBBBBBBBB
  CCCCCCCCCC
  ..........
  ..........
  ..........
  ..........`;

  setMap(map);
  setBackground('D');
  updateStateText();

  const nonSpaceMap = map.split("").filter(x => x !== " " && x !== "\n")
  const mapHeight = height();
  const mapWidth = width();

  // UI elements
  // The \x09 is intentional. A drum sprite will add sprites \x00 to \x08.
  addSprite(1, 2, '\x09');
  
  const beatmap = globalState.peekBack().beatmap;
  const diff = globalState.current().beatmapDifficulty;

  const tracks = beatmap.music;
  
  // Transform the notes into a more usable format
  // [[appear time, hit time, note, scroll time], ...]
  const unsortedNotes = [];
  {
    let currentScrollSpeed = 1;
    for (const [time, note, scrollSpeed] of diff.notes) {
      if (scrollSpeed) {
        currentScrollSpeed = scrollSpeed;
      }

      // Amount of time the note appears on the screen
      const scrollTime = Math.round(1000 / currentScrollSpeed);
      
      unsortedNotes.push({
        // Appear time
        appear: time - scrollTime,

        // Hit time
        hit: time,

        // Note
        note,

        // Scroll time
        scrollTime,
      });
    }
  }

  // Sort notes based on when they are hit
  const notes = unsortedNotes.sort((a, b) => a.hit > b.hit);
  
  // Keep track of the visible notes on the screen
  //   as an alternative to running .filter on a potentially
  //   massive array every few milliseconds.
  // Or not.
  // let lastVisibleIndex = -1;
  
  // Time when the song starts (in x milliseconds)
  const start = Date.now() + 2000;
  setTimeout(() => music.play(), 2000); // The song is loaded in the song selection menu

  // Used in the "Draw notes" interval
  let slice = [];
  let notesLegend = [];
  let bufferedNotes = [];
  let previousNotes = [];
  let previousLegend = [];

  let loopState = true;

  // TODO: window.requestAnimationFrame is more optimized on web
  const loops = [
    // Non-rendering functions
    setInterval(() => {
      // Clear misses
      const time = Date.now() - start;

      while (notes.length > 0 && time - notes[0].hit >= MILLIS_MISS) {
        state.combo = 0;
        state.accuracies.push(0);
        notes.shift();
        updateStateText();
      }

      // Go to results screen once there are no more notes
      if (notes.length == 0 && loopState) {
        loopState = false;

        setTimeout(() => {
          for (const intervalId of loops) {
            clearInterval(intervalId);
          }
          
          clearText();
          clearInputEvents();
  
          gotoResultsScreen();
        }, 1000);

        return;
      }
    }),

    // Draw notes
    setInterval(() => {
      const time = Date.now() - start;
  
      // Get all visible notes and sort by hit timings
      // const slice = notes.slice(0, lastVisibleIndex).sort((a, b) => a.appear > b.appear);
      slice = notes.filter((note) => note.appear <= time);
  
      // Stop if there are no visible notes
      if (slice.length == 0) {
        return;
      }
  
      // Sprig determines the z-index of each sprite by referring to the legend.
      // Sprites that are defined first are placed higher. In order for notes to
      // appear correctly, we need to redefine the legend appropriately.

      let charCode = 91;
  
      // Loop from earlier appearance (above) to later appearance (below)
      for (const note of slice) {
        // The x-coordinate is determined by:
        // 1. Finding how long it has been since the note appeared
        // 2. Dividing it by how long it can remain on the screen
        //    to get a value from 0 to 1
        // 3. Collapsing this value to the sprite grid
        // 4. Set the sprite to its appropriate subsprite
        const progress = (time - note.appear) / note.scrollTime;
        const xSubCoord = (width() - 2) * progress;
        const xCoord = width() - Math.floor(xSubCoord) - 1;
  
        // Don't render note if it is already gone
        if (xCoord < 0) {
          continue;
        }

        // This would mean that everything but 0 is a blue note, but let's leave that fact
        // as an implementation quirk ;)
        const noteSet = note.note === 0 ? redNotes : blueNotes;
        const spriteIndex = Math.floor(xSubCoord % 1 * 16);
  
        // Add sprites to legend
        const mainSprite = String.fromCharCode(charCode++);
        const sprite = noteSet[spriteIndex];
        notesLegend.push([mainSprite, sprite[0]]);

        if (xCoord > 0) { // x coordinate must be at least 1 for a transition sprite
          const transitionSprite = String.fromCharCode(charCode++);
          notesLegend.push([transitionSprite, sprite[1]]);
        }
  
        // Add sprites to a buffer to be drawn once the legend is fully built
        bufferedNotes.push(xCoord);
      }

      // Don't redraw if nothing has changed since the previous iteration
      if (bufferedNotes.every((note, i) => note === previousNotes[i]) &&
        // The sprite value always remains the same
          notesLegend.every((sprite, i) => sprite[1] === previousLegend[i][1])) {
        // Clear the current notes legend
        notesLegend.length = 0;
      
        // Clear the notes buffer
        bufferedNotes.length = 0;
        return;
      }

      // Refresh sprites without using setMap
      getGrid().forEach((tile, index) => {
        if (tile.length > 0) {
          tile[0]._type = nonSpaceMap[index];
        }

        for (let i = 1; i < tile.length; i++) {
          tile[i].remove();
        }
      });
      addSprite(1, 2, '\x09');

      // Set the generated legend
      setLegend(...notesLegend, ...legendKeys);

      // Draw buffered notes
      charCode = 91;
      for (const xCoord of bufferedNotes) {
        addSprite(xCoord, 2, String.fromCharCode(charCode++));
        if (xCoord > 0) {
          addSprite(xCoord - 1, 2, String.fromCharCode(charCode++));
        }
      }

      // Store the current legend and notes
      previousLegend = notesLegend;
      previousNotes = bufferedNotes;
  
      // Clear the current notes legend and buffer
      notesLegend = [];
      bufferedNotes = [];
    }),
  ];
  
  // Quit the run when a player taps UP 3 times.
  let quitCounter = 0;
  
  const quitRun = () => {
    if (++quitCounter < 3) {
      return;
    }
  
    clearText();
    clearInputEvents();

    for (const intervalId of loops) {
      clearInterval(intervalId);
    }

    // Remove the game state
    globalState.pop();

    drawSelectDifficultyScreen();
  };
  const checkHit = (hitNote) => {
    const note = notes[0];
    if (!note) return;

    const diff = Math.abs((Date.now() - start) - note.hit);

    // Ignore note if beyond the miss range
    if (diff >= MILLIS_MISS) {
      return;
    }

    // Mark wrong hits a miss
    if (note.note != hitNote) {
      state.accuracies.push(0);
      notes.shift();
      state.combo = 0;
      updateStateText();
      return;
    }

    // Score algorithm derived from: https://osu.ppy.sh/wiki/en/Gameplay/Score/ScoreV1/osu%21taiko

    if (0 <= diff && diff < MILLIS_GREAT) {
      state.accuracies.push(100);
      state.score += 300 + Math.min(Math.floor(state.combo / 10), 10);
    } else if (MILLIS_GREAT <= diff && diff < MILLIS_OK) {
      state.accuracies.push(50);
      state.score += 150 + Math.min(Math.floor(state.combo / 10), 10);
    } else {
      state.accuracies.push(0);
    }
    
    notes.shift();

    // Update combo depending on if the player missed or not
    state.combo++;

    if (state.maxCombo < state.combo) {
      state.maxCombo = state.combo;
    }

    updateStateText();
  };
  
  inputEvents.w = quitRun;
  inputEvents.i = quitRun;
  
  inputEvents.a = () => {
    checkHit(1);
    quitCounter = 0;
    playTune(hitsoundKat);
  };

  inputEvents.d = () => {
    checkHit(0);
    quitCounter = 0;
    playTune(hitsoundDon);
  };

  inputEvents.j = () => {
    checkHit(0);
    quitCounter = 0;
    playTune(hitsoundDon);
  };

  inputEvents.l = () => {
    checkHit(1);
    quitCounter = 0;
    playTune(hitsoundKat);
  };
}

function drawResultsScreen() {
  const state = globalState.current();

  setLegend(
    ['O', bitmap`
LLLLLLLLLLLLLLLL
L222222L22LLL22L
L222222L22LLL22L
L22LL22L22L22LLL
L22LL22L22L22LLL
L22LL22L22L22LLL
L22LL22L222LLLLL
L22LL22L222LLLLL
L22LL22L222LLLLL
L22LL22L222LLLLL
L22LL22L22L22LLL
L22LL22L22L22LLL
L22LL22L22L22LLL
L222222L22LLL22L
L222222L22LLL22L
LLLLLLLLLLLLLLLL`],
    ['K', bitmap`
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLL2222LLLLLL
LLLLLL2222LLLLLL
LLLLLL2222LLLLLL
LLLLLL2222LLLLLL
LL1111LLLL1111LL
LL1111LLLL1111LL
LL1111LLLL1111LL
LL1111LLLL1111LL
LLLLLL1111LLLLLL
LLLLLL1111LLLLLL
LLLLLL1111LLLLLL
LLLLLL1111LLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL`],
    ['A', bitmap`
................
................
................
................
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111`],
    ['B', bitmap`
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111`],
    ['C', bitmap`
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
................
................
................
................
................`],
    ['D', bitmap`
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL
LLLLLLLLLLLLLLLL`],
  );
  
  setMap(`
  ........OK
  ..........
  AAAAAAAAAA
  BBBBBBBBBB
  BBBBBBBBBB
  CCCCCCCCCC
  ..........
  ..........`);
  setBackground('D');

  addText(state.songTitle, {
    x: 0,
    y: 5,
    color: color`2`
  });

  addText(state.diffTitle, {
    x: 0,
    y: 6,
    color: color`2`
  });

  addText(state.score + ' score', {
    x: 0,
    y: 8,
    color: color`2`
  });

  addText(state.accuracy + '%', {
    x: 0,
    y: 9,
    color: color`2`
  });

  addText(state.combo + 'x combo', {
    x: 0,
    y: 10,
    color: color`2`
  });

  addText(state.hitCount.great + ' GREAT', {
    x: 0,
    y: 12,
    color: color`7`
  });

  addText(state.hitCount.ok + ' OK', {
    x: 0,
    y: 13,
    color: color`6`
  });

  addText(state.hitCount.miss + ' MISS', {
    x: 0,
    y: 14,
    color: color`3`
  });
  
  const quitToMenu = () => {
    clearText();
    clearInputEvents();

    // Remove the results state
    globalState.pop();

    drawSelectDifficultyScreen();
  };
  inputEvents.w = quitToMenu;
  inputEvents.i = quitToMenu;
}

/**************************
 *        Game init       *
 **************************/
const globalState = new GlobalState();
const music = new Music(); 

drawTitleScreen();
